package com.jet.chat.server;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

import com.jet.chat.dbquery.ChatQueryAddUser;
import com.jet.chat.dbquery.ChatQueryAddUserParams;
import com.jet.chat.dbquery.ChatQueryIsUserExist;
import com.jet.chat.dbquery.ChatQueryIsUserExistParams;
import com.jet.chat.dbquery.ChatQueryIsUserExistResult;
import com.jet.chat.event.ActionListener;
import com.jet.chat.event.ActionSimple;
import com.jet.chat.exception.ChatException;
import com.jet.chat.exception.ChatExceptionCheckAuthSQLException;
import com.jet.chat.exception.ChatExceptionCommandClassNotFound;
import com.jet.chat.exception.ChatExceptionCommandIllegalAccess;
import com.jet.chat.exception.ChatExceptionCommandInstantiation;
import com.jet.chat.exception.ChatExceptionMessageRead;
import com.jet.chat.message.Message;
import com.jet.chat.message.MessageQueue;
import com.jet.chat.message.MessageQueueService;
import com.jet.chat.message.MessageReader;
import com.jet.chat.message.MessageReaderObjectInputStream;
import com.jet.chat.message.MessageWriter;
import com.jet.chat.message.MessageWriterObjectOutputStream;
import com.jet.chat.service.ChatAccessService;
import com.jet.chat.service.MessageService;


public class ChatSession implements Runnable {
	private ChatServer chatServer;
	private Socket socket;
	private MessageReader reader;
	private MessageWriter writer;
	private int userId;
	private String username;
	private String channelName;
	private MessageQueue queue;
	
	public ChatSession(ChatServer chatServer, String channelName, Socket socket) {
		this.chatServer = chatServer;
		this.channelName = channelName;
		this.socket = socket;
		//System.out.println("ChatSession Constructor: in");
		
		try {
			this.reader = new MessageReaderObjectInputStream(new ObjectInputStream(this.socket.getInputStream()));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//System.out.println("ChatSession Constructor: reader done");
		
		try {
			this.writer = new MessageWriterObjectOutputStream(new ObjectOutputStream(this.socket.getOutputStream()));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//System.out.println("ChatSession Constructor: writer done");
		
	}
	
	public MessageWriter getWriter() {
		return writer;
	}

	public int getUserId() {
		return userId;
	}

	public String getUsername() {
		return username;
	}
	
	public String getChannelName() {
		return channelName;
	}
	
	public void setChannelName(String channelName) {
		this.channelName = channelName;
	}
	
	public MessageQueue getQueue() {
		return queue;
	}

	private synchronized boolean login(ChatAccessService chatAccessService) {
		try {
			
			chatAccessService.checkAuth(this.reader, this.queue);
			this.userId = chatAccessService.getId();
			this.username = chatAccessService.getUsername();

			if (this.userId == 0) {
				return false;
			} else {
				ChatSession chatSession = null;
				for (ChatChannel chatChannel : this.chatServer.getChannels().values()) {
					chatSession = chatChannel.getSession(this.username);
					if (chatSession != null) {
						//String reason = "your session was terminated due to login from other place";
						//chatSession.logout(reason);
						chatServer.getChannel(chatSession.channelName).removeSession(chatSession);
						chatSession.getQueue().addMessage("bye");
						chatSession.getQueue().addMessage("/q");
						chatSession.socket.close();
					}
				}

				chatServer.getChannel(this.channelName).addSession(this);
				
				Message message = new Message("/vu");
				message.setReverse(true);
				this.queue.addMessage(message);
				
				for (ActionListener listener : chatServer.getActionListeners("onLogin")) {
					listener.actionPerformed(new ActionSimple(this.username + " joined chat"));
				}
				
				return true;
			}	
		} catch (ChatExceptionMessageRead e) {
			// TODO Auto-generated catch block
			e.printStackTrace(); //[ek] TODO should user get this message?
		} catch (ChatExceptionCheckAuthSQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return false;
	}
	
	public synchronized void logout(String reason) throws ChatException {
		chatServer.getChannel(this.channelName).removeSession(this);
		this.queue.addMessage(reason);
		this.queue.addMessage("/q");
		System.out.println(this.username + " removed from channel '" + this.channelName + "'");
		try {
			if (socket != null && !socket.isClosed()) {
				socket.close();
				System.out.println(this.username + "'s socket closed");
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public boolean toRegister() {
		try {
			this.queue.addMessage("register: y/n?");
			if (this.reader.read().getContent().equals("y")) {
				return true;
			} else {
				return false;
			}
		} catch (ChatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return false;
	}
	
	public void register() {
		try {
			this.queue.addMessage("username?");
			String username = this.reader.read().getContent();
			ChatQueryIsUserExistResult result = (ChatQueryIsUserExistResult) new ChatQueryIsUserExist().execute(chatServer.getDBConnectionService(), new ChatQueryIsUserExistParams(username));
			
			while (result.getResult()) {
				this.queue.addMessage("another username?");
				username = this.reader.read().getContent();
				result = (ChatQueryIsUserExistResult) new ChatQueryIsUserExist().execute(chatServer.getDBConnectionService(), new ChatQueryIsUserExistParams(username));
			}
			
			this.queue.addMessage("password?");
			String password = this.reader.read().getContent();
			new ChatQueryAddUser().execute(chatServer.getDBConnectionService(), new ChatQueryAddUserParams(username, password));

		} catch (ChatException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void run() {
		MessageQueueService messageQueueService = new MessageQueueService(this.writer);
		Thread messageQueueThread = new Thread(messageQueueService);
		this.queue = messageQueueService.getMessageQueue();
		messageQueueThread.start();

		ChatAccessService chatAccessService = new ChatAccessService(chatServer);
		
		if (toRegister()) {
			register();
		}
		
		if (login(chatAccessService)) {
			try {
				while(!socket.isClosed()) {
					proceed(this.reader.read());
				}
			} catch (ChatExceptionMessageRead e) {
				//e.printStackTrace();
			} catch (IOException e) {
				//e.printStackTrace();
			} catch (ChatException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {
				if (!socket.isClosed()) {
					chatServer.getChannel(channelName).removeSession(this);
					try {
						socket.close();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println("finallized session '" + this.username + "'");
				}
			}
		}
	}
	
	
	//[DF] Too long lock 
	private void proceed(Message chatMessage) throws ChatExceptionMessageRead, IOException {
		
		if (socket.isClosed()) {
			System.out.println(this.username + " : socket.isClosed()");
			//System.out.println(this.username + " leaved chat: socket.isClosed()");
			//chatServer.getChannel(this.channelName).removeSession(this);
			return;
		}
		
		if (chatMessage == null) {
			socket.close();
			//System.out.println(this.username + " : chatMessage == null");
			System.out.println(this.username + " leaved chat: chatMessage == null");
			chatServer.getChannel(this.channelName).removeSession(this);
			return;
		}
		
		if (chatMessage.getContent() == null) {
			socket.close();
			//System.out.println(this.username + " : chatMessage.getText() == null");
			System.out.println(this.username + " leaved chat: chatMessage.getText() == null");
			chatServer.getChannel(this.channelName).removeSession(this);
			return;
		}
		
    	try {
    		MessageService.proceed(chatServer, this, chatMessage);
    	} catch (ChatExceptionCommandClassNotFound e) {
    		e.printStackTrace();
    	} catch (ChatExceptionCommandInstantiation e) {
    		e.printStackTrace();
    	} catch (ChatExceptionCommandIllegalAccess e) {
    		e.printStackTrace();
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
	}
	
}
